/*
 * Copyright 2011 Christoph Ludwig (christoph[at]chludwig.de). All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.chludwig.ehumanities.provenance.provider;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.text.MessageFormat;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;

import org.apache.abdera.model.Document;
import org.apache.abdera.model.Element;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;

/**
 * Infrastructure for serializing and de-serializing both Atom feeds and Atom entries.
 * <p>
 * The implementation follows closely the <a href="http://blogs.oracle.com/sandoz/entry/jersey_and_abdera_with_a">example</a>
 * in <a href="http://blogs.oracle.com/sandoz/">Paul Sandoz's blog</a> at Oracle.</p> 
 */
public abstract class AbderaProviderBase implements MessageBodyWriter<Object>, MessageBodyReader<Object> {

  /*
   * (non-Javadoc)
   * 
   * @see javax.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class,
   * java.lang.reflect.Type, java.lang.annotation.Annotation[],
   * javax.ws.rs.core.MediaType)
   */
  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return isAssignableToFeedOrEntry(type);
  }

  /*
   * (non-Javadoc)
   * 
   * @see javax.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class,
   * java.lang.reflect.Type, java.lang.annotation.Annotation[],
   * javax.ws.rs.core.MediaType)
   */
  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return isAssignableToFeedOrEntry(type);
  }

  /**
   * Indicates that the size of the message body cannot be determined in advance
   * by always returning -1.
   * 
   * @see javax.ws.rs.ext.MessageBodyWriter#getSize(java.lang.Object,
   *      java.lang.Class, java.lang.reflect.Type,
   *      java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType)
   */
  @Override
  public long getSize(Object feedOrEntry, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return -1;
  }

  @Override
  public Feed readFrom(Class<Object> feedOrEntry, Type genericType, Annotation[] annotations, MediaType mediaType,
          MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException,
          WebApplicationException {
    Document<Element> doc = read(entityStream);
    Element el = doc.getRoot();
    if (feedOrEntry.isAssignableFrom(el.getClass())) {
      return (Feed) el;
    }
    else {
      String msg = MessageFormat.format("Unexpected payload, expected {0}, received {1}", feedOrEntry.getName(), el
              .getClass().getName());
      throw new IOException(msg);
    }
  }

  @Override
  public void writeTo(Object feedOrEntry, Class<?> type, Type genericType, Annotation[] annotations,
          MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
          throws IOException, WebApplicationException {
    if (feedOrEntry instanceof Feed) {
      Feed feed = (Feed) feedOrEntry;
      write(feed.getDocument(), entityStream);
    }
    else {
      Entry entry = (Entry) feedOrEntry;
      write(entry.getDocument(), entityStream);
    }
  }

  /**
   * Reads the entityStream using an implementation specific parser.
   * @param entityStream The stream the serialized element is read from.
   * @return An Abdera document representing the element read.
   */
  protected abstract Document<Element> read(InputStream entityStream);

  /**
   * Serializes the document using an implementation specific writer.
   * @param document The document to be serialized.
   * @param entityStream The stream where the serialized object is written to.
   * @throws IOException The document could either not be serialized or writing to entityStream triggered an error.
   */
  protected abstract void write(Document<Element> document, OutputStream entityStream) throws IOException;

  /**
   * Predicate that tests whether the argument is an inclusive subclass of {@link Feed} or {@link Entry}.
   *  
   * @param type the class to be tested. 
   * @return true iff an instance of type can be assigned to a reference to Feed or to a reference to Entry.
   */
  private boolean isAssignableToFeedOrEntry(Class<?> type) {
    return Feed.class.isAssignableFrom(type) || Entry.class.isAssignableFrom(type);
  }

}
